using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Inspired.Euler
{
    public static class Problem054
    {
        /// <summary>
        /// How many hands did player one win in the game of poker?
        /// </summary>
        [EulerProblem(54, Title = "How many hands did player one win in the game of poker?")]
        public static long Solve()
        {
            return
                //File.ReadAllLines(@"..\..\Data\poker.txt")
                new string[]
                {
                    "5H 5C 6S 7S KD 2C 3S 8S 8D TD", 
                    "5D 8C 9S JS AC 2C 5C 7D 8S QH",
                    "2D 9C AS AH AC 3D 6D 7D TD QD",
                    "4D 6S 9H QH QC 3D 6D 7H QD QS",
                    "2H 2D 4C 4D 4S 3C 3D 3S 9S 9D"
                }
                    .Select(draw => draw.Split(' '))
                    .Select(cards => new
                    {
                        Player1 = new Player(cards.Take(5)),
                        Player2 = new Player(cards.Skip(5).Take(5))
                    })
                    .Count(game => game.Player1 > game.Player2);
        }

        [Flags]
        enum RankEnum
        {
            None = 0,               // None
            HighCard = 1,           // Highest value card.
            OnePair = 2,            // Two cards of the same value.
            TwoPairs = 4,           // Two different pairs.
            ThreeOfAKind = 8,       // Three cards of the same value.
            Straight = 16,          // All cards are consecutive values.
            Flush = 32,             // All cards of the same suit.
            FullHouse = 64,         // Three of a kind and a pair.
            FourOfAKind = 128,      // Four cards of the same value.
            StraightFlush = 256,    // All cards are consecutive values of same suit.
            RoyalFlush = 512        // Ten, Jack, Queen, King, Ace, in same suit.
        }

        enum Suit
        {
            Spades,
            Hearts,
            Diamonds,
            Clubs
        }

        class Card
        {
            static Type suit = typeof(Suit);
            static Dictionary<string, int> values = new Dictionary<string, int>()
            {
                { "2", 01 }, { "3", 02 }, { "4", 03 }, { "5", 04 },
                { "6", 05 }, { "7", 06 }, { "8", 07 }, { "9", 08 },
                { "T", 09 }, { "J", 10 }, { "Q", 11 }, { "K", 12 }, { "A", 13 }
            };

            public string Letter { get; private set; }
            public int Value { get; private set; }
            public Suit Suit { get; private set; }
            public Card(string card)
            {
                this.Letter = card.Substring(0, card.Length - 1);
                this.Value = values[this.Letter];
                this.Suit = (Suit)
                    Enum.Parse(suit,
                        Enum.GetNames(suit)
                            .First(name => name
                                .StartsWith(card.Substring(card.Length - 1))));
            }
        }

        class Rank
        {
            public RankEnum Value { get; set; }
            public Card Card { get; set; }
        }

        class Player
        {
            #region GetRanks
            static Func<Card[], RankEnum>[] getRanks = new Func<Card[], RankEnum>[]
            {
                #region RoyalFlush: Ten, Jack, Queen, King, Ace, in same suit
                cards => { return cards
                    .All(card => card.Suit == cards[0].Suit) &&
                    cards.Any(card => card.Letter == "10") && 
                    cards.Any(card => card.Letter == "J") && 
                    cards.Any(card => card.Letter == "Q") && 
                    cards.Any(card => card.Letter == "K") && 
                    cards.Any(card => card.Letter == "A") ? RankEnum.RoyalFlush: RankEnum.None; },
                #endregion

                #region StraightFlush: All cards are consecutive values of same suit
                cards => { return (
                    (cards[0].Value - 0) == (cards[1].Value - 1) &&
                    (cards[1].Value - 0) == (cards[2].Value - 1) &&
                    (cards[2].Value - 0) == (cards[3].Value - 1)) &&
                    cards.All(card => card.Suit == cards[0].Suit) ? RankEnum.StraightFlush: RankEnum.None; },
                #endregion

                #region FourOfAKind: Four cards of the same value
                cards => { return cards
                    .GroupBy(card => card.Value)
                    .Where(group => group.Count() == 4)
                    .Any() ? RankEnum.FourOfAKind: RankEnum.None; },
                #endregion

                #region FullHouse: Three of a kind and a pair
                cards => { return cards
                    .GroupBy(card => card.Value)
                    .Where(group => group.Count() == 3).Any() && cards
                    .GroupBy(card => card.Value)
                    .Where(group => group.Count() == 2).Count() == 1 ? RankEnum.FullHouse: RankEnum.None; },
                #endregion

                #region Flush: All cards of the same suit
                cards => { return cards.All(card => card.Suit == cards[0].Suit) ? RankEnum.Flush: RankEnum.None; },
                #endregion

                #region Straight: All cards are consecutive values
                cards => { return (
                    (cards[0].Value - 0) == (cards[1].Value - 1) &&
                    (cards[1].Value - 0) == (cards[2].Value - 1) &&
                    (cards[2].Value - 0) == (cards[3].Value - 1)) ? RankEnum.Straight: RankEnum.None; },
                #endregion

                #region ThreeOfAKind: Three cards of the same value
                cards => { return cards
                    .GroupBy(card => card.Value)
                    .Where(group => group.Count() == 3)
                    .Any() ? RankEnum.ThreeOfAKind: RankEnum.None; },
                #endregion

                #region TwoPairs: Two different pairs
                cards => { return cards
                    .GroupBy(card => card.Value)
                    .Where(group => group.Count() == 2)
                    .Count() == 2 ? RankEnum.TwoPairs: RankEnum.None; },
                #endregion

                #region OnePair: Two cards of the same value
                cards => { return cards
                    .GroupBy(card => card.Value)
                    .Where(group => group.Count() == 2)
                    .Count() == 1 ? RankEnum.OnePair: RankEnum.None; },
                #endregion 

                #region HighCard: Highest value card
                cards => { return RankEnum.HighCard; }
                #endregion
            };
            #endregion

            public Card[] Hand { get; private set; }
            public RankEnum Rank { get; private set; }
            public Player(IEnumerable<string> cards)
            {
                Hand = cards.Select(card => new Card(card)).ToArray();
                Rank = getRanks.Select(getRank => getRank(Hand)).Max();
            }

            public static bool operator < (Player x, Player y)
            {
                return !(x > y);
            }
            public static bool operator > (Player x, Player y)
            {
                return x.Rank > y.Rank;
            }
        }

        class CardComparer : IComparer<Card>
        {
            public int Compare(Card x, Card y)
            {
                return x.Value - y.Value;
            }
        }
    }
}